home *** CD-ROM | disk | FTP | other *** search
- /*
- * tclUnixUtil.c --
- *
- * This file contains a collection of utility procedures that
- * are present in the Tcl's UNIX core but not in the generic
- * core. For example, they do file manipulation and process
- * manipulation.
- *
- * Parts of this file are based on code contributed by Karl
- * Lehenbauer, Mark Diekhans and Peter da Silva.
- *
- * Copyright (c) 1991-1994 The Regents of the University of California.
- * Copyright (c) 1994 Sun Microsystems, Inc.
- *
- * See the file "license.terms" for information on usage and redistribution
- * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
- */
-
- #ifndef lint
- static char sccsid[] = "@(#) tclUnixUtil.c 1.55 95/03/29 11:24:23";
- #endif /* not lint */
-
- #include "tclInt.h"
- #include "tclPort.h"
-
- /*
- * A linked list of the following structures is used to keep track
- * of child processes that have been detached but haven't exited
- * yet, so we can make sure that they're properly "reaped" (officially
- * waited for) and don't lie around as zombies cluttering the
- * system.
- */
-
- typedef struct Detached {
- int pid; /* Id of process that's been detached
- * but isn't known to have exited. */
- struct Detached *nextPtr; /* Next in list of all detached
- * processes. */
- } Detached;
-
- static Detached *detList = NULL; /* List of all detached proceses. */
-
- /*
- * The following variables are used to keep track of all the open files
- * in the process. These files can be shared across interpreters, so the
- * information can't be put in the Interp structure.
- */
-
- int tclNumFiles = 0; /* Number of entries in tclOpenFiles below.
- * 0 means array hasn't been created yet. */
- OpenFile **tclOpenFiles; /* Pointer to malloc-ed array of pointers
- * to information about open files. Entry
- * N corresponds to the file with fileno N.
- * If an entry is NULL then the corresponding
- * file isn't open. If tclOpenFiles is NULL
- * it means no files have been used, so even
- * stdin/stdout/stderr entries haven't been
- * setup yet. */
-
- /*
- * Declarations for local procedures defined in this file:
- */
-
- static int FileForRedirect _ANSI_ARGS_((Tcl_Interp *interp,
- char *spec, int atOk, char *arg, int flags,
- char *nextArg, int *skipPtr, int *closePtr));
- static void MakeFileTable _ANSI_ARGS_((Interp *iPtr, int index));
- static void RestoreSignals _ANSI_ARGS_((void));
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_EvalFile --
- *
- * Read in a file and process the entire file as one gigantic
- * Tcl command.
- *
- * Results:
- * A standard Tcl result, which is either the result of executing
- * the file or an error indicating why the file couldn't be read.
- *
- * Side effects:
- * Depends on the commands in the file.
- *
- *----------------------------------------------------------------------
- */
-
- int
- Tcl_EvalFile(interp, fileName)
- Tcl_Interp *interp; /* Interpreter in which to process file. */
- char *fileName; /* Name of file to process. Tilde-substitution
- * will be performed on this name. */
- {
- int fileId, result;
- struct stat statBuf;
- char *cmdBuffer, *oldScriptFile;
- Interp *iPtr = (Interp *) interp;
- Tcl_DString buffer;
-
- Tcl_ResetResult(interp);
- oldScriptFile = iPtr->scriptFile;
- iPtr->scriptFile = fileName;
- fileName = Tcl_TildeSubst(interp, fileName, &buffer);
- if (fileName == NULL) {
- goto error;
- }
- fileId = open(fileName, O_RDONLY, 0);
- if (fileId < 0) {
- Tcl_AppendResult(interp, "couldn't read file \"", fileName,
- "\": ", Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- if (fstat(fileId, &statBuf) == -1) {
- Tcl_AppendResult(interp, "couldn't stat file \"", fileName,
- "\": ", Tcl_PosixError(interp), (char *) NULL);
- close(fileId);
- goto error;
- }
- cmdBuffer = (char *) ckalloc((unsigned) statBuf.st_size+1);
- if (read(fileId, cmdBuffer, (size_t) statBuf.st_size) != statBuf.st_size) {
- Tcl_AppendResult(interp, "error in reading file \"", fileName,
- "\": ", Tcl_PosixError(interp), (char *) NULL);
- close(fileId);
- ckfree(cmdBuffer);
- goto error;
- }
- if (close(fileId) != 0) {
- Tcl_AppendResult(interp, "error closing file \"", fileName,
- "\": ", Tcl_PosixError(interp), (char *) NULL);
- ckfree(cmdBuffer);
- goto error;
- }
- cmdBuffer[statBuf.st_size] = 0;
- result = Tcl_Eval(interp, cmdBuffer);
- if (result == TCL_RETURN) {
- result = TclUpdateReturnInfo(iPtr);
- } else if (result == TCL_ERROR) {
- char msg[200];
-
- /*
- * Record information telling where the error occurred.
- */
-
- sprintf(msg, "\n (file \"%.150s\" line %d)", fileName,
- interp->errorLine);
- Tcl_AddErrorInfo(interp, msg);
- }
- ckfree(cmdBuffer);
- iPtr->scriptFile = oldScriptFile;
- Tcl_DStringFree(&buffer);
- return result;
-
- error:
- iPtr->scriptFile = oldScriptFile;
- Tcl_DStringFree(&buffer);
- return TCL_ERROR;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_DetachPids --
- *
- * This procedure is called to indicate that one or more child
- * processes have been placed in background and will never be
- * waited for; they should eventually be reaped by
- * Tcl_ReapDetachedProcs.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tcl_DetachPids(numPids, pidPtr)
- int numPids; /* Number of pids to detach: gives size
- * of array pointed to by pidPtr. */
- int *pidPtr; /* Array of pids to detach. */
- {
- register Detached *detPtr;
- int i;
-
- for (i = 0; i < numPids; i++) {
- detPtr = (Detached *) ckalloc(sizeof(Detached));
- detPtr->pid = pidPtr[i];
- detPtr->nextPtr = detList;
- detList = detPtr;
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_ReapDetachedProcs --
- *
- * This procedure checks to see if any detached processes have
- * exited and, if so, it "reaps" them by officially waiting on
- * them. It should be called "occasionally" to make sure that
- * all detached processes are eventually reaped.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Processes are waited on, so that they can be reaped by the
- * system.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tcl_ReapDetachedProcs()
- {
- register Detached *detPtr;
- Detached *nextPtr, *prevPtr;
- int status, result;
-
- for (detPtr = detList, prevPtr = NULL; detPtr != NULL; ) {
- result = waitpid(detPtr->pid, &status, WNOHANG);
- if ((result == 0) || ((result == -1) && (errno != ECHILD))) {
- prevPtr = detPtr;
- detPtr = detPtr->nextPtr;
- continue;
- }
- nextPtr = detPtr->nextPtr;
- if (prevPtr == NULL) {
- detList = detPtr->nextPtr;
- } else {
- prevPtr->nextPtr = detPtr->nextPtr;
- }
- ckfree((char *) detPtr);
- detPtr = nextPtr;
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_CreatePipeline --
- *
- * Given an argc/argv array, instantiate a pipeline of processes
- * as described by the argv.
- *
- * Results:
- * The return value is a count of the number of new processes
- * created, or -1 if an error occurred while creating the pipeline.
- * *pidArrayPtr is filled in with the address of a dynamically
- * allocated array giving the ids of all of the processes. It
- * is up to the caller to free this array when it isn't needed
- * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
- * with the file id for the input pipe for the pipeline (if any):
- * the caller must eventually close this file. If outPipePtr
- * isn't NULL, then *outPipePtr is filled in with the file id
- * for the output pipe from the pipeline: the caller must close
- * this file. If errFilePtr isn't NULL, then *errFilePtr is filled
- * with a file id that may be used to read error output after the
- * pipeline completes.
- *
- * Side effects:
- * Processes and pipes are created.
- *
- *----------------------------------------------------------------------
- */
-
- int
- Tcl_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
- outPipePtr, errFilePtr)
- Tcl_Interp *interp; /* Interpreter to use for error reporting. */
- int argc; /* Number of entries in argv. */
- char **argv; /* Array of strings describing commands in
- * pipeline plus I/O redirection with <,
- * <<, >, etc. Argv[argc] must be NULL. */
- int **pidArrayPtr; /* Word at *pidArrayPtr gets filled in with
- * address of array of pids for processes
- * in pipeline (first pid is first process
- * in pipeline). */
- int *inPipePtr; /* If non-NULL, input to the pipeline comes
- * from a pipe (unless overridden by
- * redirection in the command). The file
- * id with which to write to this pipe is
- * stored at *inPipePtr. -1 means command
- * specified its own input source. */
- int *outPipePtr; /* If non-NULL, output to the pipeline goes
- * to a pipe, unless overriden by redirection
- * in the command. The file id with which to
- * read frome this pipe is stored at
- * *outPipePtr. -1 means command specified
- * its own output sink. */
- int *errFilePtr; /* If non-NULL, all stderr output from the
- * pipeline will go to a temporary file
- * created here, and a descriptor to read
- * the file will be left at *errFilePtr.
- * The file will be removed already, so
- * closing this descriptor will be the end
- * of the file. If this is NULL, then
- * all stderr output goes to our stderr.
- * If the pipeline specifies redirection
- * then the fill will still be created
- * but it will never get any data. */
- {
- int *pidPtr = NULL; /* Points to malloc-ed array holding all
- * the pids of child processes. */
- int numPids = 0; /* Actual number of processes that exist
- * at *pidPtr right now. */
- int cmdCount; /* Count of number of distinct commands
- * found in argc/argv. */
- char *input = NULL; /* If non-null, then this points to a
- * string containing input data (specified
- * via <<) to be piped to the first process
- * in the pipeline. */
- int inputId = -1; /* If >= 0, gives file id to use as input for
- * first process in pipeline (specified via
- * < or <@). */
- int closeInput = 0; /* If non-zero, then must close inputId
- * when cleaning up (zero means the file needs
- * to stay open for some other reason). */
- int outputId = -1; /* Writable file id for output from last
- * command in pipeline (could be file or pipe).
- * -1 means use stdout. */
- int closeOutput = 0; /* Non-zero means must close outputId when
- * cleaning up (similar to closeInput). */
- int errorId = -1; /* Writable file id for error output from
- * all commands in pipeline. -1 means use
- * stderr. */
- int closeError = 0; /* Non-zero means must close errorId when
- * cleaning up. */
- int pipeIds[2]; /* File ids for pipe that's being created. */
- int firstArg, lastArg; /* Indexes of first and last arguments in
- * current command. */
- int skip; /* Number of arguments to skip (because they
- * specify redirection). */
- int maxFd; /* Highest known file descriptor (used to
- * close off extraneous file descriptors in
- * child process). */
- int errPipeIds[2]; /* Used for communication between parent and
- * child processes. If child encounters
- * error during startup it returns error
- * message via pipe. If child starts up
- * OK, it closes pipe without anything in
- * it. */
- int lastBar;
- char *execName;
- int i, j, pid, count;
- char *p;
- Tcl_DString buffer;
- char errSpace[200];
-
- if (inPipePtr != NULL) {
- *inPipePtr = -1;
- }
- if (outPipePtr != NULL) {
- *outPipePtr = -1;
- }
- if (errFilePtr != NULL) {
- *errFilePtr = -1;
- }
- pipeIds[0] = pipeIds[1] = -1;
- errPipeIds[0] = errPipeIds[1] = -1;
-
- /*
- * First, scan through all the arguments to figure out the structure
- * of the pipeline. Process all of the input and output redirection
- * arguments and remove them from the argument list in the pipeline.
- * Count the number of distinct processes (it's the number of "|"
- * arguments plus one) but don't remove the "|" arguments.
- */
-
- cmdCount = 1;
- lastBar = -1;
- for (i = 0; i < argc; i++) {
- if ((argv[i][0] == '|') && (((argv[i][1] == 0))
- || ((argv[i][1] == '&') && (argv[i][2] == 0)))) {
- if ((i == (lastBar+1)) || (i == (argc-1))) {
- interp->result = "illegal use of | or |& in command";
- return -1;
- }
- lastBar = i;
- cmdCount++;
- continue;
- } else if (argv[i][0] == '<') {
- if ((inputId >= 0) && closeInput) {
- close(inputId);
- }
- inputId = -1;
- skip = 1;
- if (argv[i][1] == '<') {
- input = argv[i]+2;
- if (*input == 0) {
- input = argv[i+1];
- if (input == 0) {
- Tcl_AppendResult(interp, "can't specify \"", argv[i],
- "\" as last word in command", (char *) NULL);
- goto error;
- }
- skip = 2;
- }
- } else {
- input = 0;
- inputId = FileForRedirect(interp, argv[i]+1, 1, argv[i],
- O_RDONLY, argv[i+1], &skip, &closeInput);
- if (inputId < 0) {
- goto error;
- }
- }
- } else if (argv[i][0] == '>') {
- int append, useForStdErr, useForStdOut, mustClose, fd, atOk, flags;
-
- skip = atOk = 1;
- append = useForStdErr = 0;
- useForStdOut = 1;
- if (argv[i][1] == '>') {
- p = argv[i] + 2;
- append = 1;
- atOk = 0;
- flags = O_WRONLY|O_CREAT;
- } else {
- p = argv[i] + 1;
- flags = O_WRONLY|O_CREAT|O_TRUNC;
- }
- if (*p == '&') {
- useForStdErr = 1;
- p++;
- }
- fd = FileForRedirect(interp, p, atOk, argv[i], flags, argv[i+1],
- &skip, &mustClose);
- if (fd < 0) {
- goto error;
- }
- if (append) {
- lseek(fd, 0L, 2);
- }
-
- /*
- * Got the file descriptor. Now use it for standard output,
- * standard error, or both, depending on the redirection.
- */
-
- if (useForStdOut) {
- if ((outputId > 0) && closeOutput) {
- close(outputId);
- }
- outputId = fd;
- closeOutput = mustClose;
- }
- if (useForStdErr) {
- if ((errorId > 0) && closeError) {
- close(errorId);
- }
- errorId = fd;
- closeError = (useForStdOut) ? 0 : mustClose;
- }
- } else if ((argv[i][0] == '2') && (argv[i][1] == '>')) {
- int append, atOk, flags;
-
- if ((errorId > 0) && closeError) {
- close(errorId);
- }
- skip = 1;
- p = argv[i] + 2;
- if (*p == '>') {
- p++;
- append = 1;
- atOk = 0;
- flags = O_WRONLY|O_CREAT;
- } else {
- append = 0;
- atOk = 1;
- flags = O_WRONLY|O_CREAT|O_TRUNC;
- }
- errorId = FileForRedirect(interp, p, atOk, argv[i], flags,
- argv[i+1], &skip, &closeError);
- if (errorId < 0) {
- goto error;
- }
- if (append) {
- lseek(errorId, 0L, 2);
- }
- } else {
- continue;
- }
- for (j = i+skip; j < argc; j++) {
- argv[j-skip] = argv[j];
- }
- argc -= skip;
- i -= 1; /* Process next arg from same position. */
- }
- if (argc == 0) {
- interp->result = "didn't specify command to execute";
- return -1;
- }
-
- if (inputId < 0) {
- if (input != NULL) {
- char inName[L_tmpnam];
- int length;
-
- /*
- * The input for the first process is immediate data coming from
- * Tcl. Create a temporary file for it and put the data into the
- * file.
- */
-
- tmpnam(inName);
- inputId = open(inName, O_RDWR|O_CREAT|O_TRUNC, 0600);
- closeInput = 1;
- if (inputId < 0) {
- Tcl_AppendResult(interp,
- "couldn't create input file \"", inName,
- "\" for command: ", Tcl_PosixError(interp),
- (char *) NULL);
- goto error;
- }
- length = strlen(input);
- if (write(inputId, input, (size_t) length) != length) {
- Tcl_AppendResult(interp,
- "couldn't write file input for command: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- if ((lseek(inputId, 0L, 0) == -1) || (unlink(inName) == -1)) {
- Tcl_AppendResult(interp,
- "couldn't reset or remove input file for command: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- } else if (inPipePtr != NULL) {
- /*
- * The input for the first process in the pipeline is to
- * come from a pipe that can be written from this end.
- */
-
- if (pipe(pipeIds) != 0) {
- Tcl_AppendResult(interp,
- "couldn't create input pipe for command: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- inputId = pipeIds[0];
- closeInput = 1;
- *inPipePtr = pipeIds[1];
- pipeIds[0] = pipeIds[1] = -1;
- }
- }
-
- /*
- * Set up a pipe to receive output from the pipeline, if no other
- * output sink has been specified.
- */
-
- if ((outputId < 0) && (outPipePtr != NULL)) {
- if (pipe(pipeIds) != 0) {
- Tcl_AppendResult(interp,
- "couldn't create output pipe: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- outputId = pipeIds[1];
- closeOutput = 1;
- *outPipePtr = pipeIds[0];
- pipeIds[0] = pipeIds[1] = -1;
- }
-
- /*
- * Set up the standard error output sink for the pipeline, if
- * requested. Use a temporary file which is opened, then deleted.
- * Could potentially just use pipe, but if it filled up it could
- * cause the pipeline to deadlock: we'd be waiting for processes
- * to complete before reading stderr, and processes couldn't complete
- * because stderr was backed up.
- */
-
- if (errFilePtr != NULL) {
- char errName[L_tmpnam];
-
- tmpnam(errName);
- *errFilePtr = open(errName, O_RDONLY|O_CREAT|O_TRUNC, 0600);
- if (*errFilePtr < 0) {
- errFileError:
- Tcl_AppendResult(interp,
- "couldn't create error file \"", errName,
- "\" for command: ", Tcl_PosixError(interp),
- (char *) NULL);
- goto error;
- }
- if (errorId < 0) {
- errorId = open(errName, O_WRONLY|O_CREAT|O_TRUNC, 0600);
- if (errorId < 0) {
- goto errFileError;
- }
- closeError = 1;
- }
- if (unlink(errName) == -1) {
- Tcl_AppendResult(interp,
- "couldn't remove error file for command: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- }
-
- /*
- * Find the largest file descriptor used so far, so that we can
- * clean up all the extraneous file descriptors in the child
- * processes we create.
- */
-
- maxFd = inputId;
- if (outputId > maxFd) {
- maxFd = outputId;
- }
- if (errorId > maxFd) {
- maxFd = errorId;
- }
- if ((inPipePtr != NULL) && (*inPipePtr > maxFd)) {
- maxFd = *inPipePtr;
- }
- if ((outPipePtr != NULL) && (*outPipePtr > maxFd)) {
- maxFd = *outPipePtr;
- }
- if ((errFilePtr != NULL) && (*errFilePtr > maxFd)) {
- maxFd = *errFilePtr;
- }
-
- /*
- * Scan through the argc array, forking off a process for each
- * group of arguments between "|" arguments.
- */
-
- pidPtr = (int *) ckalloc((unsigned) (cmdCount * sizeof(int)));
- for (i = 0; i < numPids; i++) {
- pidPtr[i] = -1;
- }
- Tcl_ReapDetachedProcs();
- for (firstArg = 0; firstArg < argc; numPids++, firstArg = lastArg+1) {
- int joinThisError;
- int curOutputId;
-
- joinThisError = 0;
- for (lastArg = firstArg; lastArg < argc; lastArg++) {
- if (argv[lastArg][0] == '|') {
- if (argv[lastArg][1] == 0) {
- break;
- }
- if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == 0)) {
- joinThisError = 1;
- break;
- }
- }
- }
- argv[lastArg] = NULL;
- if (lastArg == argc) {
- curOutputId = outputId;
- } else {
- if (pipe(pipeIds) != 0) {
- Tcl_AppendResult(interp, "couldn't create pipe: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- curOutputId = pipeIds[1];
- if (pipeIds[0] > maxFd) {
- maxFd = pipeIds[0];
- }
- if (pipeIds[1] > maxFd) {
- maxFd = pipeIds[1];
- }
- }
-
- /*
- * Create a pipe that the child can use to return error
- * information if anything goes wrong. Set the close-on-exec
- * flag for the write end of the pipe so that it will be
- * closed automatically if the child succesfully execs the
- * new subprocess.
- */
-
- if (pipe(errPipeIds) != 0) {
- Tcl_AppendResult(interp, "couldn't create pipe: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- if (errPipeIds[0] > maxFd) {
- maxFd = errPipeIds[0];
- }
- if (errPipeIds[1] > maxFd) {
- maxFd = errPipeIds[1];
- }
- if (fcntl(errPipeIds[1], F_SETFD, FD_CLOEXEC) != 0) {
- Tcl_AppendResult(interp, "couldn't set close on exec for pipe: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
- execName = Tcl_TildeSubst(interp, argv[firstArg], &buffer);
- pid = vfork();
- if (pid == 0) {
- if (((inputId != -1) && (dup2(inputId, 0) == -1))
- || ((curOutputId != -1) && (dup2(curOutputId, 1) == -1))
- || (joinThisError && (dup2(1, 2) == -1))
- || (!joinThisError && (errorId != -1)
- && (dup2(errorId, 2) == -1))) {
- sprintf(errSpace,
- "%dforked process couldn't set up input/output: ",
- errno);
- write(errPipeIds[1], errSpace, (size_t) strlen(errSpace));
- _exit(1);
- }
- for (i = 3; i <= maxFd; i++) {
- if (i != errPipeIds[1]) {
- close(i);
- }
- }
- RestoreSignals();
- execvp(execName, &argv[firstArg]);
- sprintf(errSpace, "%dcouldn't execute \"%.150s\": ", errno,
- argv[firstArg]);
- write(errPipeIds[1], errSpace, (size_t) strlen(errSpace));
- _exit(1);
- }
- Tcl_DStringFree(&buffer);
- if (pid == -1) {
- Tcl_AppendResult(interp, "couldn't fork child process: ",
- Tcl_PosixError(interp), (char *) NULL);
- goto error;
- }
-
- /*
- * Read back from the error pipe to see if the child startup
- * up OK. The info in the pipe (if any) consists of a decimal
- * errno value followed by an error message.
- */
-
- close(errPipeIds[1]);
- errPipeIds[1] = -1;
- count = read(errPipeIds[0], errSpace, (size_t) (sizeof(errSpace) - 1));
- if (count > 0) {
- char *end;
- errSpace[count] = 0;
- errno = strtol(errSpace, &end, 10);
- Tcl_AppendResult(interp, end, Tcl_PosixError(interp),
- (char *) NULL);
- goto error;
- }
- close(errPipeIds[0]);
- errPipeIds[0] = -1;
- pidPtr[numPids] = pid;
-
- /*
- * Close off our copies of file descriptors that were set up for
- * this child, then set up the input for the next child.
- */
-
- if ((inputId != -1) && closeInput) {
- close(inputId);
- }
- if ((curOutputId != -1) && (curOutputId != outputId)) {
- close(curOutputId);
- }
- inputId = pipeIds[0];
- closeInput = 1;
- pipeIds[0] = pipeIds[1] = -1;
- }
- *pidArrayPtr = pidPtr;
-
- /*
- * All done. Cleanup open files lying around and then return.
- */
-
- cleanup:
- if ((inputId != -1) && closeInput) {
- close(inputId);
- }
- if ((outputId != -1) && closeOutput) {
- close(outputId);
- }
- if ((errorId != -1) && closeError) {
- close(errorId);
- }
- return numPids;
-
- /*
- * An error occurred. There could have been extra files open, such
- * as pipes between children. Clean them all up. Detach any child
- * processes that have been created.
- */
-
- error:
- if ((inPipePtr != NULL) && (*inPipePtr != -1)) {
- close(*inPipePtr);
- *inPipePtr = -1;
- }
- if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
- close(*outPipePtr);
- *outPipePtr = -1;
- }
- if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
- close(*errFilePtr);
- *errFilePtr = -1;
- }
- if (pipeIds[0] != -1) {
- close(pipeIds[0]);
- }
- if (pipeIds[1] != -1) {
- close(pipeIds[1]);
- }
- if (errPipeIds[0] != -1) {
- close(errPipeIds[0]);
- }
- if (errPipeIds[1] != -1) {
- close(errPipeIds[1]);
- }
- if (pidPtr != NULL) {
- for (i = 0; i < numPids; i++) {
- if (pidPtr[i] != -1) {
- Tcl_DetachPids(1, &pidPtr[i]);
- }
- }
- ckfree((char *) pidPtr);
- }
- numPids = -1;
- goto cleanup;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * FileForRedirect --
- *
- * This procedure does much of the work of parsing redirection
- * operators. It handles "@" if specified and allowed, and a file
- * name, and opens the file if necessary.
- *
- * Results:
- * The return value is the descriptor number for the file. If an
- * error occurs then -1 is returned and an error message is left
- * in interp->result. Several arguments are side-effected; see
- * the argument list below for details.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- static int
- FileForRedirect(interp, spec, atOk, arg, flags, nextArg, skipPtr, closePtr)
- Tcl_Interp *interp; /* Intepreter to use for error
- * reporting. */
- register char *spec; /* Points to character just after
- * redirection character. */
- int atOk; /* Non-zero means '@' notation is
- * OK, zero means it isn't. */
- char *arg; /* Pointer to entire argument
- * containing spec: used for error
- * reporting. */
- int flags; /* Flags to use for opening file. */
- char *nextArg; /* Next argument in argc/argv
- * array, if needed for file name.
- * May be NULL. */
- int *skipPtr; /* This value is incremented if
- * nextArg is used for redirection
- * spec. */
- int *closePtr; /* This value is set to 1 if the file
- * that's returned must be closed, 0
- * if it was specified with "@" so
- * it must be left open. */
- {
- int writing = (flags & O_WRONLY);
- FILE *f;
- int fd;
-
- if (atOk && (*spec == '@')) {
- spec++;
- if (*spec == 0) {
- spec = nextArg;
- if (spec == NULL) {
- goto badLastArg;
- }
- *skipPtr += 1;
- }
- if (Tcl_GetOpenFile(interp, spec, writing, 1, &f) != TCL_OK) {
- return -1;
- }
- *closePtr = 0;
- fd = fileno(f);
- if (writing) {
- /*
- * Be sure to flush output to the file, so that anything
- * written by the child appears after stuff we've already
- * written.
- */
-
- fflush(f);
- }
- } else {
- if (*spec == 0) {
- spec = nextArg;
- if (spec == NULL) {
- goto badLastArg;
- }
- *skipPtr += 1;
- }
- fd = open(spec, flags, 0666);
- if (fd < 0) {
- Tcl_AppendResult(interp, "couldn't ",
- (writing) ? "write" : "read", " file \"", spec, "\": ",
- Tcl_PosixError(interp), (char *) NULL);
- return -1;
- }
- *closePtr = 1;
- }
- return fd;
-
- badLastArg:
- Tcl_AppendResult(interp, "can't specify \"", arg,
- "\" as last word in command", (char *) NULL);
- return -1;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * RestoreSignals --
- *
- * This procedure is invoked in a forked child process just before
- * exec-ing a new program to restore all signals to their default
- * settings.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Signal settings get changed.
- *
- *----------------------------------------------------------------------
- */
-
- static void
- RestoreSignals()
- {
- #ifdef SIGABRT
- signal(SIGABRT, SIG_DFL);
- #endif
- #ifdef SIGALRM
- signal(SIGALRM, SIG_DFL);
- #endif
- #ifdef SIGFPE
- signal(SIGFPE, SIG_DFL);
- #endif
- #ifdef SIGHUP
- signal(SIGHUP, SIG_DFL);
- #endif
- #ifdef SIGILL
- signal(SIGILL, SIG_DFL);
- #endif
- #ifdef SIGINT
- signal(SIGINT, SIG_DFL);
- #endif
- #ifdef SIGPIPE
- signal(SIGPIPE, SIG_DFL);
- #endif
- #ifdef SIGQUIT
- signal(SIGQUIT, SIG_DFL);
- #endif
- #ifdef SIGSEGV
- signal(SIGSEGV, SIG_DFL);
- #endif
- #ifdef SIGTERM
- signal(SIGTERM, SIG_DFL);
- #endif
- #ifdef SIGUSR1
- signal(SIGUSR1, SIG_DFL);
- #endif
- #ifdef SIGUSR2
- signal(SIGUSR2, SIG_DFL);
- #endif
- #ifdef SIGCHLD
- signal(SIGCHLD, SIG_DFL);
- #endif
- #ifdef SIGCONT
- signal(SIGCONT, SIG_DFL);
- #endif
- #ifdef SIGTSTP
- signal(SIGTSTP, SIG_DFL);
- #endif
- #ifdef SIGTTIN
- signal(SIGTTIN, SIG_DFL);
- #endif
- #ifdef SIGTTOU
- signal(SIGTTOU, SIG_DFL);
- #endif
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_PosixError --
- *
- * This procedure is typically called after UNIX kernel calls
- * return errors. It stores machine-readable information about
- * the error in $errorCode returns an information string for
- * the caller's use.
- *
- * Results:
- * The return value is a human-readable string describing the
- * error, as returned by strerror.
- *
- * Side effects:
- * The global variable $errorCode is reset.
- *
- *----------------------------------------------------------------------
- */
-
- char *
- Tcl_PosixError(interp)
- Tcl_Interp *interp; /* Interpreter whose $errorCode variable
- * is to be changed. */
- {
- char *id, *msg;
-
- id = Tcl_ErrnoId();
- msg = strerror(errno);
- Tcl_SetErrorCode(interp, "POSIX", id, msg, (char *) NULL);
- return msg;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * MakeFileTable --
- *
- * Create or enlarge the file table for the interpreter, so that
- * there is room for a given index.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The file table for iPtr will be created if it doesn't exist
- * (and entries will be added for stdin, stdout, and stderr).
- * If it already exists, then it will be grown if necessary.
- *
- *----------------------------------------------------------------------
- */
-
- /* ARGSUSED */
- static void
- MakeFileTable(iPtr, index)
- Interp *iPtr; /* Interpreter whose table of files is
- * to be manipulated. */
- int index; /* Make sure table is large enough to
- * hold at least this index. */
- {
- /*
- * If the table doesn't even exist, then create it and initialize
- * entries for standard files.
- */
-
- if (tclNumFiles == 0) {
- OpenFile *oFilePtr;
- int i;
-
- if (index < 2) {
- tclNumFiles = 3;
- } else {
- tclNumFiles = index+1;
- }
- tclOpenFiles = (OpenFile **) ckalloc((unsigned)
- ((tclNumFiles)*sizeof(OpenFile *)));
- for (i = tclNumFiles-1; i >= 0; i--) {
- tclOpenFiles[i] = NULL;
- }
-
- oFilePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
- oFilePtr->f = stdin;
- oFilePtr->f2 = NULL;
- oFilePtr->permissions = TCL_FILE_READABLE;
- oFilePtr->numPids = 0;
- oFilePtr->pidPtr = NULL;
- oFilePtr->errorId = -1;
- tclOpenFiles[0] = oFilePtr;
-
- oFilePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
- oFilePtr->f = stdout;
- oFilePtr->f2 = NULL;
- oFilePtr->permissions = TCL_FILE_WRITABLE;
- oFilePtr->numPids = 0;
- oFilePtr->pidPtr = NULL;
- oFilePtr->errorId = -1;
- tclOpenFiles[1] = oFilePtr;
-
- oFilePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
- oFilePtr->f = stderr;
- oFilePtr->f2 = NULL;
- oFilePtr->permissions = TCL_FILE_WRITABLE;
- oFilePtr->numPids = 0;
- oFilePtr->pidPtr = NULL;
- oFilePtr->errorId = -1;
- tclOpenFiles[2] = oFilePtr;
- } else if (index >= tclNumFiles) {
- int newSize;
- OpenFile **newPtrArray;
- int i;
-
- newSize = index+1;
- newPtrArray = (OpenFile **) ckalloc((unsigned)
- ((newSize)*sizeof(OpenFile *)));
- memcpy((VOID *) newPtrArray, (VOID *) tclOpenFiles,
- tclNumFiles*sizeof(OpenFile *));
- for (i = tclNumFiles; i < newSize; i++) {
- newPtrArray[i] = NULL;
- }
- ckfree((char *) tclOpenFiles);
- tclNumFiles = newSize;
- tclOpenFiles = newPtrArray;
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_EnterFile --
- *
- * This procedure is used to enter an already-open file into the
- * file table for an interpreter so that the file can be read
- * and written with Tcl commands.
- *
- * Results:
- * There is no return value, but interp->result is set to
- * hold Tcl's id for the open file, such as "file4".
- *
- * Side effects:
- * "File" is added to the files accessible from interp.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tcl_EnterFile(interp, file, permissions)
- Tcl_Interp *interp; /* Interpreter in which to make file
- * available. */
- FILE *file; /* File to make available in interp. */
- int permissions; /* Ops that may be done on file: OR-ed
- * combinination of TCL_FILE_READABLE and
- * TCL_FILE_WRITABLE. */
- {
- Interp *iPtr = (Interp *) interp;
- int fd;
- register OpenFile *oFilePtr;
-
- fd = fileno(file);
- if (fd >= tclNumFiles) {
- MakeFileTable(iPtr, fd);
- }
- oFilePtr = tclOpenFiles[fd];
-
- /*
- * It's possible that there already appears to be a file open in
- * the slot. This could happen, for example, if the application
- * closes a file behind our back so that we don't have a chance
- * to clean up. This is probably a bad idea, but if it happens
- * just discard the information in the old record (hopefully the
- * application is smart enough to have really cleaned everything
- * up right).
- */
-
- if (oFilePtr == NULL) {
- oFilePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
- tclOpenFiles[fd] = oFilePtr;
- }
- oFilePtr->f = file;
- oFilePtr->f2 = NULL;
- oFilePtr->permissions = permissions;
- oFilePtr->numPids = 0;
- oFilePtr->pidPtr = NULL;
- oFilePtr->errorId = -1;
- if (fd <= 2) {
- if (fd == 0) {
- interp->result = "stdin";
- } else if (fd == 1) {
- interp->result = "stdout";
- } else {
- interp->result = "stderr";
- }
- } else {
- sprintf(interp->result, "file%d", fd);
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_GetOpenFile --
- *
- * Given a string identifier for an open file, find the corresponding
- * open file structure, if there is one.
- *
- * Results:
- * A standard Tcl return value. If the open file is successfully
- * located and meets any usage check requested by checkUsage, TCL_OK
- * is returned and *filePtr is modified to hold a pointer to its
- * FILE structure. If an error occurs then TCL_ERROR is returned
- * and interp->result contains an error message.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- int
- Tcl_GetOpenFile(interp, string, forWriting, checkUsage, filePtr)
- Tcl_Interp *interp; /* Interpreter in which to find file. */
- char *string; /* String that identifies file. */
- int forWriting; /* 1 means the file is going to be used
- * for writing, 0 means for reading. */
- int checkUsage; /* 1 means verify that the file was opened
- * in a mode that allows the access specified
- * by "forWriting". */
- FILE **filePtr; /* Store pointer to FILE structure here. */
- {
- OpenFile *oFilePtr;
- int fd = 0; /* Initial value needed only to stop compiler
- * warnings. */
- Interp *iPtr = (Interp *) interp;
-
- if ((string[0] == 'f') && (string[1] == 'i') && (string[2] == 'l')
- & (string[3] == 'e')) {
- char *end;
-
- fd = strtoul(string+4, &end, 10);
- if ((end == string+4) || (*end != 0)) {
- goto badId;
- }
- } else if ((string[0] == 's') && (string[1] == 't')
- && (string[2] == 'd')) {
- if (strcmp(string+3, "in") == 0) {
- fd = 0;
- } else if (strcmp(string+3, "out") == 0) {
- fd = 1;
- } else if (strcmp(string+3, "err") == 0) {
- fd = 2;
- } else {
- goto badId;
- }
- } else {
- badId:
- Tcl_AppendResult(interp, "bad file identifier \"", string,
- "\"", (char *) NULL);
- return TCL_ERROR;
- }
-
- if (fd >= tclNumFiles) {
- if ((tclNumFiles == 0) && (fd <= 2)) {
- MakeFileTable(iPtr, fd);
- } else {
- notOpen:
- Tcl_AppendResult(interp, "file \"", string, "\" isn't open",
- (char *) NULL);
- return TCL_ERROR;
- }
- }
- oFilePtr = tclOpenFiles[fd];
- if (oFilePtr == NULL) {
- goto notOpen;
- }
- if (forWriting) {
- if (checkUsage && !(oFilePtr->permissions & TCL_FILE_WRITABLE)) {
- Tcl_AppendResult(interp, "\"", string,
- "\" wasn't opened for writing", (char *) NULL);
- return TCL_ERROR;
- }
- if (oFilePtr->f2 != NULL) {
- *filePtr = oFilePtr->f2;
- } else {
- *filePtr = oFilePtr->f;
- }
- } else {
- if (checkUsage && !(oFilePtr->permissions & TCL_FILE_READABLE)) {
- Tcl_AppendResult(interp, "\"", string,
- "\" wasn't opened for reading", (char *) NULL);
- return TCL_ERROR;
- }
- *filePtr = oFilePtr->f;
- }
- return TCL_OK;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_FilePermissions --
- *
- * Given a FILE * pointer, return the read/write permissions
- * associated with the open file.
- *
- * Results:
- * If file is currently open, the return value is an OR-ed
- * combination of TCL_FILE_READABLE and TCL_FILE_WRITABLE,
- * which indicates the operations permitted on the open file.
- * If the file isn't open then the return value is -1.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- int
- Tcl_FilePermissions(file)
- FILE *file; /* File for which permissions are wanted. */
- {
- register OpenFile *oFilePtr;
- int i, fd;
-
- /*
- * First try the entry in tclOpenFiles given by the file descriptor
- * for the file. If that doesn't match then search all the entries
- * in tclOpenFiles.
- */
-
- if (file != NULL) {
- fd = fileno(file);
- if (fd < tclNumFiles) {
- oFilePtr = tclOpenFiles[fd];
- if ((oFilePtr != NULL) && (oFilePtr->f == file)) {
- return oFilePtr->permissions;
- }
- }
- }
- for (i = 0; i < tclNumFiles; i++) {
- oFilePtr = tclOpenFiles[i];
- if (oFilePtr == NULL) {
- continue;
- }
- if ((oFilePtr->f == file) || (oFilePtr->f2 == file)) {
- return oFilePtr->permissions;
- }
- }
- return -1;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * TclOpen, etc. --
- *
- * Below are a bunch of procedures that are used by Tcl instead
- * of system calls. Each of the procedures executes the
- * corresponding system call and retries automatically
- * if the system call was interrupted by a signal.
- *
- * Results:
- * Whatever the system call would normally return.
- *
- * Side effects:
- * Whatever the system call would normally do.
- *
- * NOTE:
- * This should be the last page of this file, since it undefines
- * the macros that redirect read etc. to the procedures below.
- *
- *----------------------------------------------------------------------
- */
-
- #undef open
- int
- TclOpen(path, oflag, mode)
- char *path;
- int oflag;
- int mode;
- {
- int result;
- while (1) {
- result = open(path, oflag, mode);
- if ((result != -1) || (errno != EINTR)) {
- return result;
- }
- }
- }
-
- #undef read
- int
- TclRead(fd, buf, numBytes)
- int fd;
- VOID *buf;
- size_t numBytes;
- {
- int result;
- while (1) {
- result = read(fd, buf, (size_t) numBytes);
- if ((result != -1) || (errno != EINTR)) {
- return result;
- }
- }
- }
-
- #undef waitpid
- extern pid_t waitpid _ANSI_ARGS_((pid_t pid, int *stat_loc, int options));
-
- /*
- * Note: the #ifdef below is needed to avoid compiler errors on systems
- * that have ANSI compilers and also define pid_t to be short. The
- * problem is a complex one having to do with argument type promotion.
- */
-
- #ifdef _USING_PROTOTYPES_
- int
- TclWaitpid _ANSI_ARGS_((pid_t pid, int *statPtr, int options))
- #else
- int
- TclWaitpid(pid, statPtr, options)
- pid_t pid;
- int *statPtr;
- int options;
- #endif /* _USING_PROTOTYPES_ */
- {
- int result;
- while (1) {
- result = waitpid(pid, statPtr, options);
- if ((result != -1) || (errno != EINTR)) {
- return result;
- }
- }
- }
-
- #undef write
- int
- TclWrite(fd, buf, numBytes)
- int fd;
- VOID *buf;
- size_t numBytes;
- {
- int result;
- while (1) {
- result = write(fd, buf, (size_t) numBytes);
- if ((result != -1) || (errno != EINTR)) {
- return result;
- }
- }
- }
-